/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

package assignment3;

import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Random;
import java.util.Vector;

/**
 *
 * @author cjaramillo
 */
public class GeneticAlgorithm 
{
    Vector<Chromosome> m_population;
    Vector<Chromosome> m_currentGeneration;
    Vector<Double> m_averages;
    Vector<Double> m_best;
    double[] m_generationDistance;
    double m_crossoverProbability;
    int m_geneCount;
    int m_chromosomeCount;
    int[] m_rouletteWheel;
    PrintWriter m_writer;
    
    GeneticAlgorithm() throws FileNotFoundException, UnsupportedEncodingException
    {
        m_population = new Vector<Chromosome>();
        m_currentGeneration = new Vector<Chromosome>();
        m_rouletteWheel = new int[100];
        m_averages = new Vector<Double>();
        m_best = new Vector<Double>();
        String filename = "Assignment3Results.txt";
        m_writer = new PrintWriter(filename, "UTF-8");
        m_writer.println("best;average");
    }
    
    public void createInitialPopulation(int count)
    {
        Random random = new Random();
        for(int i=0; i<count; i++)
        {
            double val = Math.random();
            if(val < m_crossoverProbability)
            {
                performCrossover();
            }
            else
            {
                performMutation();
            }
        }
    }
        
    public void increasePopulation(int multiplier)
    {
        Random random = new Random();
        int generationChromosomeCount = multiplier * m_chromosomeCount;
        m_currentGeneration = new Vector<Chromosome>();
        for(int i=0; i<generationChromosomeCount; i++)
        {
            double val = Math.random();
            if(val < m_crossoverProbability)
            {
                performCrossover();
            }
            else
            {
                performMutation();
            }
        }
    }
        
    public void calculateFitness()
    {
        //find total of distance fitness
        int generationSize = m_generationDistance.length;
        double total = 0.0;
        double [] chromosomeFitness = new double[generationSize];
        double [] finalChromosomeFitness = new double[generationSize];
        for(int i=0; i<generationSize; i++)
        {
            total += m_generationDistance[i];
        }
        //figure out what percent each chromosome is of total
        for(int j=0; j<generationSize; j++)
        {
            double fit = (total-m_generationDistance[j])/total;
            chromosomeFitness[j] = fit;
        }
        double adjustedTotal = 0.0;
        for(int k=0; k<generationSize; k++)
        {
            adjustedTotal += chromosomeFitness[k];
        }
        for(int l=0; l<generationSize; l++)
        {
             finalChromosomeFitness[l] = chromosomeFitness[l]/adjustedTotal;
        }
        double sum = 0.0;
        for(int h=0;h<generationSize;h++)
        {
            sum+=chromosomeFitness[h];
        }
        System.out.println("Sum: "+sum);
        //fill the array
        int amountFilled = 0;
        int bestIndex = 0;
        for(int k=0; k<generationSize; k++)
        {
            if(m_generationDistance[k] < m_generationDistance[bestIndex])
            {
                bestIndex = k;
            }
            int fillAmount = (int) Math.floor(chromosomeFitness[k]+0.5);
            int start = 0;
            while(amountFilled<100 && start<fillAmount)
            {
                m_rouletteWheel[amountFilled]=k;
                amountFilled++;
                start++;
            }
        }
        Double newAverage = new Double(total/generationSize);
        m_averages.add(newAverage);
        m_best.add(new Double(m_generationDistance[bestIndex]));
        m_writer.println(m_generationDistance[bestIndex]+";"+ newAverage);
    }
    
    private void selectNextGeneration()
    {
        //loop as many times as we want chromosomes
        Vector<Chromosome> newGeneration = new Vector<Chromosome>();
        Random random = new Random();
        int[] choices = new int[m_chromosomeCount];
        Arrays.fill(choices, -1);
        int chromosomesChosen = 0;
        while(chromosomesChosen < m_chromosomeCount)
        {
            //pick a random number 0-99
            int choice = m_rouletteWheel[random.nextInt(100)];
            //if that number hasn't been picked before put it in the population
            boolean taken = false;
            for(int i = 0; i < m_chromosomeCount; i++)
            {
                if(choices[i]==choice)
                {
                    taken = true;
                }
            }
            if(!taken)
            {
                choices[chromosomesChosen] = choice;
                chromosomesChosen++;
            }
            //if it has, pick another number
        }
        for(int j=0;j<m_chromosomeCount;j++)
        {
            newGeneration.add(m_currentGeneration.elementAt(choices[j]));
        }
        m_population = newGeneration;
        System.out.println(Arrays.toString(choices));
    }
    
    private boolean testConvergence()
    {
        int generations = m_best.size();
        if(generations>1)
        {
           System.out.println("Best generation Difference: " + (m_best.elementAt(generations-1)-m_best.elementAt(generations-2)));
           if(Math.abs(m_best.elementAt(generations-1)-m_best.elementAt(generations-2)) < 0.1)
           {
               System.out.println("Average generation Difference: " + (m_averages.elementAt(generations-1)-m_averages.elementAt(generations-2)));
               if(Math.abs((m_averages.elementAt(generations-1)-m_averages.elementAt(generations-2))) < 5)
               {
               System.out.println("Took " + (generations-1) + " generations.");
               return true;
               }
           }
        }

        return false;
    }
    
    private void performCrossover()
    {
        Random random = new Random();
        int firstIndex = random.nextInt(m_population.size());
        int secondIndex = random.nextInt(m_population.size());
        while(firstIndex == secondIndex)
        {
            secondIndex = random.nextInt(m_population.size());
        }
        Chromosome first = m_population.elementAt(firstIndex);
        m_currentGeneration.add(new Chromosome(first.crossover(m_population.elementAt(secondIndex))));
    }
    
    private void performMutation()
    {
        Random random = new Random();
        int index = random.nextInt(m_population.size());
        m_currentGeneration.add(new Chromosome(m_population.elementAt(index).mutate()));
    }

    void generateInitialPopulation(int chromosomeCount, int geneCount) 
    {
        m_chromosomeCount = chromosomeCount;
        m_geneCount = geneCount;
        for(int i=0; i<chromosomeCount;i++)
        {
            Chromosome newChromosome = new Chromosome(geneCount);
            newChromosome.generate();
            m_population.add(newChromosome);
        }
    }

    void crossoverProbability(double crossoverProbability) 
    {
        m_crossoverProbability = crossoverProbability;
    }

    void calculateGenerationDistances() 
    {
        int generationSize = m_currentGeneration.size();
        m_generationDistance = new double[generationSize];
        for(int i=0; i<generationSize; i++)
        {
            m_generationDistance[i] = m_currentGeneration.elementAt(i).calculateDistance();
        }
    }

    boolean selectNextPopulation() 
    {
        this.increasePopulation(2);
        this.calculateGenerationDistances();
        this.calculateFitness();
        if(this.testConvergence())
        {
            m_writer.close();
            return true;
        }
        this.selectNextGeneration();
        return false;
    }
    public void closeWriter()
    {
        m_writer.close();
    }
}

